/**
 * 
 */
package org.ordobill.opensource.telegram.input;

import static java.lang.System.err;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

import org.ordobill.opensource.telegram.config.Config;
import org.ordobill.opensource.telegram.config.property.ConfigProperty;

/**
 * The <code>Telegram</code> abstract class represents character telegram.
 * <br />
 * This class can't make instance without implement.
 * <br />
 * If you want to be implement this class then you have to   
 * 
 * @author Choi Jin Wook(A.K.A Brian Choi @ Ordobill Office) / choijinwook84@gmail.com
 * 
 */
public class Split {
    
    //The Integer array for split length for telegram
    private Integer[] splitInt;

    //The String for split character for telegram
    private String splitChar;

    //The String for telegram
    private String telegram;
    
    //The Object for wrapper
    private Object resultObject;
    
    //The boolean for flag which select split way String or Length
    //If you select to split with String then splitDiv set false 
    private boolean splitDiv = false;

    /**
     * The construct of the Split class
     */
    public Split(){ }
    
    /**
     * The construct of the Split class
     * 
     * @param config The instance any of extends Config class.
     */
    public Split(Config config){
        this.splitInt = config.getSplitInteger();
        this.splitChar = config.getSplitChar();
    }

    /**
     * The construct of the Split class
     *  
     * @param configProperty The instance any of extends ConfigProperty class.
     */
    public Split(ConfigProperty configProperty){
        this.splitInt = configProperty.getSplitInt();
        this.splitChar = configProperty.getSplitChar();
    }
    
    /**
     * The accessor split integer array
     * 
     * @return splitInt - {@code Integer Array} split telegram into integer 
     */
    public Integer[] getSplitInt(){
        return splitInt;
    }
    
    /**
     * The accessor split character
     * 
     * @return splitChar - {@code String} split telegram into character
     */
    public String getSplitChar(){
        return splitChar;
    }
    
    /**
     * telegram's Mutator
     * @param telegram the telegram to set
     */
    public void setTelegram(String telegram) {
        this.telegram = telegram;
    }
    
    /**
     * The accessor telegram string
     * 
     * @return the telegram
     */
    public String getTelegram() {
        return telegram;
    }
    
    /**
     * Dedicated split way checker
     * 
     * <p>{@code splitDiv - false} : <span style="font-weight:bold">If you set splitChar from the "configure.ini" file.</span>
     * <br />
     * {@code splitDiv - true} : <span style="font-weight:bold">If you set splitInteger from the "configure.ini" file.</span></p>
     *  
     * @throws Exception There is no setting value on the "configure.ini" file. It have to select which split way char or integer.
     */
    public boolean checkSplitWay() throws Exception{
        if(splitInt != null && splitInt.length > 0){
            splitDiv = true;
        }else if(splitChar == null && (splitInt == null && splitInt.length <= 0)){
            throw new Exception("전문 분리 기준이 잘못되어있습니다.");
        }
        
        return splitDiv;
    }
    
    /**
     * The splitter by integer array
     * <br />
     * If {@code telegram} has been initialized then split telegram into input parameter {@code Integer Array}
     * 
     * <p>
     *   <pre>
     *     {@code telegram} has been initialized with telegram "111222333444567890".
     *     {@code splitInt} has been initialized from {@code new Integer[]{3,3,3,3,6}
     *   </pre>
     *   Then you have same result as next
     *   <pre>
     *     String[] strArr = new String[]{"111", "222", "333", "444", "567890"}
     *   </pre>
     * </p>
     * 
     * @param splitInt Array of string length point
     * @return String[] Split Telegram with splitInteger to String Array
     */
    public String[] splitByIntArr(Integer[] splitInt){
        String[] stringArr = new String[splitInt.length];
        int fixInt = 0;
        int _idx = 0;
        
        for(int _length : splitInt){
            stringArr[_idx] = telegram.substring(fixInt, fixInt+_length);
            fixInt +=_length;
            _idx++;
        }
        
        return stringArr;
    }
    
    /**
     * The splitter by split character
     * 
     * @param splitChar Divide character
     * @return {@code String Array} where divide from telegram
     */
    public String[] splitByStr(String splitChar){
        return telegram.split(splitChar);
    }

    /**
     * This is splitter that divide from telegram to {@code String Array}
     * 
     * @return {@code String Array} where divide from telegram
     * 
     */
    public String[] getStringArray(){
        String[] strArr = null;
        try {
            if(checkSplitWay() == false){
                strArr = telegram.split(getSplitChar());
            }else{
                strArr = splitByIntArr(getSplitInt());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return strArr;
    }
    
    /**
     * This is wrapping to the class instance
     * 
     * @param clssName Class name what you want to return instance. 
     * @return Object What You have inputted class name when you called 
     */
    @SuppressWarnings("unused")
    public Object genericWrapper(String clssName){
        try{
            Class<?> clss = Class.forName(clssName);

            Method[] methods = clss.getMethods();
            
            int setMethodCnt = 0;
            for(Method method : methods){
                String methodName = method.getName();
                if(!methodName.startsWith("set")||(method.getGenericReturnType() != void.class)){
                    continue;
                }
                
                Type[] pType = method.getGenericParameterTypes();
                if((pType.length != 1) || String.class.isAssignableFrom(pType[0].getClass())){
                    continue;
                }
                setMethodCnt++;
            }
                    
            int methodCnt = 0;
            try {
                resultObject = clss.newInstance();
                
                for(Method method : methods){
                    Object o = null;
                    String methodName = method.getName();
                    if(!methodName.startsWith("set")||(method.getGenericReturnType() != void.class)){
                        continue;
                    }
                    
                    Type[] pType = method.getGenericParameterTypes();
                    if((pType.length != 1) || String.class.isAssignableFrom(pType[0].getClass())){
                        continue;
                    }
                    
                    if(pType.length == 1){
                        
                        method.setAccessible(true);
                        
                        if(String.class.equals(pType[0])){
                            try {
                                o = method.invoke(resultObject, getStringArray()[methodCnt]);
                                methodCnt++;
                            } catch (IllegalArgumentException x) {
                                x.printStackTrace();
                            } catch (InvocationTargetException x) {
                                x.printStackTrace();
                                Throwable cause = x.getCause();
                                err.format("invocation of %s failed: %s%n", methodName, cause.getMessage());
                            }                            
                        }
                        
                        if(int.class.equals(pType[0]) || Integer.class.equals(pType[0])){
                            try {
                                o = method.invoke(resultObject, Integer.parseInt(getStringArray()[methodCnt]));
                                methodCnt++;                                    
                            } catch (IllegalArgumentException x) {
                                x.printStackTrace();
                            } catch (InvocationTargetException x) {
                                x.printStackTrace();
                                Throwable cause = x.getCause();
                                err.format("invocation of %s failed: %s%n", methodName, cause.getMessage());
                            } 
                        }
                        
                        if(float.class.equals(pType[0]) || Float.class.equals(pType[0])){
                            try {
                                o = method.invoke(resultObject, Float.valueOf(getStringArray()[methodCnt]));
                                methodCnt++;                                    
                            } catch (IllegalArgumentException x) {
                                x.printStackTrace();
                            } catch (InvocationTargetException x) {
                                x.printStackTrace();
                                Throwable cause = x.getCause();
                                err.format("invocation of %s failed: %s%n", methodName, cause.getMessage());
                            } 
                        }
                    }
                }
            } catch (InstantiationException e1) {
                e1.printStackTrace();
            } catch (IllegalAccessException e1) {
                e1.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        
        return resultObject;
    }
    
    /**
     * This is wrapping to the class instance
     * 
     * @param obj Any instance what you want to return.  
     * @return Object What You have inputted instance when you called
     */
    @SuppressWarnings("unused")
    public Object genericWrapper(Object obj){
        Class<?> clss = obj.getClass();

        Method[] methods = clss.getMethods();
        
        int setMethodCnt = 0;
        
        for(Method method : methods){
            String methodName = method.getName();
            if(!methodName.startsWith("set")||(method.getGenericReturnType() != void.class)){
                continue;
            }
            
            Type[] pType = method.getGenericParameterTypes();
            if((pType.length != 1) || String.class.isAssignableFrom(pType[0].getClass())){
                continue;
            }
            setMethodCnt++;
        }
                
        int methodCnt = 0;
        
        try {
            resultObject = clss.newInstance();
            
            for(Method method : methods){
                Object o = null;
                String methodName = method.getName();
                if(!methodName.startsWith("set")||(method.getGenericReturnType() != void.class)){
                    continue;
                }
                
                Type[] pType = method.getGenericParameterTypes();
                if((pType.length != 1) || String.class.isAssignableFrom(pType[0].getClass())){
                    continue;
                }
                
                if(pType.length == 1){
                    
                    method.setAccessible(true);
                    
                    if(String.class.equals(pType[0])){
                        try {
                            o = method.invoke(resultObject, getStringArray()[methodCnt]);
                            methodCnt++;
                        } catch (IllegalArgumentException x) {
                            x.printStackTrace();
                        } catch (InvocationTargetException x) {
                            x.printStackTrace();
                            Throwable cause = x.getCause();
                            err.format("invocation of %s failed: %s%n", methodName, cause.getMessage());
                        }                            
                    }
                    
                    if(int.class.equals(pType[0]) || Integer.class.equals(pType[0])){
                        try {
                            o = method.invoke(resultObject, Integer.parseInt(getStringArray()[methodCnt]));
                            methodCnt++;                                    
                        } catch (IllegalArgumentException x) {
                            x.printStackTrace();
                        } catch (InvocationTargetException x) {
                            x.printStackTrace();
                            Throwable cause = x.getCause();
                            err.format("invocation of %s failed: %s%n", methodName, cause.getMessage());
                        } 
                    }
                    
                    if(float.class.equals(pType[0]) || Float.class.equals(pType[0])){
                        try {
                            o = method.invoke(resultObject, Float.valueOf(getStringArray()[methodCnt]));
                            methodCnt++;                                    
                        } catch (IllegalArgumentException x) {
                            x.printStackTrace();
                        } catch (InvocationTargetException x) {
                            x.printStackTrace();
                            Throwable cause = x.getCause();
                            err.format("invocation of %s failed: %s%n", methodName, cause.getMessage());
                        } 
                    }
                }
            }
        } catch (InstantiationException e1) {
            e1.printStackTrace();
        } catch (IllegalAccessException e1) {
            e1.printStackTrace();
        }
        return resultObject;
    }
    
}